Notebook Purpose

This notebook serves to summarize the entire visualization process going from the travel time matrix to the visualizations. That involves the following sections:

  1. Travel Time Matrix Wrangling
  2. Score Computation
  3. Isochrone Computation
  4. Dataset Wrangling Part II (NA Insertion)
  5. Interactive Visualization
  6. Map HTML Exports

0) Useful Libraries

# wrangling/convenience
library(tidyverse)
library(glue)
library(stringr)
library(sf)
library(data.table)

# visualization
library(leaflet)
library(mapview); mapviewOptions(platform = 'leafgl')
#library(ggplot2)
#library(RColorBrewer)
#library(scales)
#library(lattice)

# For pretty knitting
library(lemon)
knit_print.data.frame <- lemon_print
knit_print.tbl <- lemon_print
knit_print.summary <- lemon_print

1) Data Wrangling

Import all dissemination block data

# import dissemination blocks and keep id and pop columns
origins <- fread(file.path("../data/clean", "vancouver_db.csv"))[, .(id, pop)]
origins$pop <- str_replace_all(origins$pop, ',', '')

# change col types
origins$pop <- as.numeric(origins$pop)  
origins$id <- as.factor(origins$id)  

n_origins <- nrow(origins)
paste('Origin Rows: ', n_origins)
[1] "Origin Rows:  15197"
# Peek
head(origins)

Import all amenity data

# import amenities (Cultural/Art facilities)
destinations <- fread(file.path("../data/clean", "vancouver_facilities_2.csv"))

# see summary counts of each amenity
destinations %>% group_by(type) %>% summarise(count = n()) %>% arrange(desc(count))

# clean amenities / filter types to keep 4 most frequent amenities
target_amenities <- c('gallery', 'museum', 'library or archives', 'theatre/performance and concert hall')
destinations <- destinations %>% filter(type %in% target_amenities)
# keep only id and type columns
destinations <- destinations[ , .(id, type)]
# change col types
destinations$type <- as.factor(destinations$type)
destinations$id <- as.factor(destinations$id)  

n_amenities <- nrow(destinations)
paste('Destinations: ', n_amenities)
[1] "Destinations:  354"
head(destinations)

Import the travel time matrix

# import travel time matrix
ttm <- fread(file.path('../data/clean', 'ttm.csv'))

# convert Ids to  factor
ttm$fromId <- as.factor(ttm$fromId)
ttm$toId <- as.factor(ttm$toId)

## Replace travel times less than 1 minute to 1 minute
# This is done to prevent infinity values in the scoring since
# 1 minute is still a reasonable time for trips in the 0 - 1 min range.
ttm$avg_time <- pmax(ttm$avg_time, 1)

# add column for isochrone groupings (between function is inclusive <=)
ttm$time_group <-  with(ttm, ifelse(avg_time < 15, "15",
                          ifelse(avg_time < 30, "30",
                          ifelse(avg_time < 45, "45",
                          ifelse(avg_time < 60, "60",
                          ifelse(avg_time < 75, "75",
                          ifelse(avg_time < 90, "90", "90+")))))))

# how many origins actually have transit accessibility
paste('Origins considered:', round((length(unique(ttm$fromId))/n_origins)*100, 2), '%')
[1] "Origins considered: 94.38 %"
paste('Destinations considered:', round(length(unique(ttm$toId))/n_amenities*100, 2), '%')
[1] "Destinations considered: 97.46 %"
paste('Rows = ', nrow(ttm))
[1] "Rows =  4172482"
# peek
head(ttm)

Import amenity weights but they still have issues:

There are duplicates on IDs 4180 and 4181.

Additionally, of all amenity IDs in the travel time matrix, not all are found in the weight set.

# import amenity weights
amenity_wts <- read.csv('../data/amenity_weights/amenity_wts.csv')

# clean weights
amenity_wts <- amenity_wts[, c('id', 'Index')]
names(amenity_wts) <- c('id', 'weight')
amenity_wts$id <-  as.factor(amenity_wts$id)

# see weight distribution
#plot(density(amenity_wts$weight), main = 'Amenity Popularity Distribution')

amenity_wts %>% group_by(id) %>% summarize(n = n()) %>% arrange(desc(n))

# are all the ttm amenity IDs in the weighted IDs set?
paste('Are all the ttm amenity IDs in the weighted IDs set? (needs to be true for the join to work)')
[1] "Are all the ttm amenity IDs in the weighted IDs set? (needs to be true for the join to work)"
all(unique(ttm$toId) %in% unique(amenity_wts$id))
[1] FALSE
# join when weight index is corrected
#destinations <- left_join(destinations, amenity_wts, by = c('id' = 'id'))

Isochrone Frame and Types


# only consider the nearest amenity
isochrone_frame <- ttm %>%
  group_by(fromId, type) %>%
  summarise(avg_time = min(avg_time))
`summarise()` has grouped output by 'fromId'. You can override using the `.groups` argument.
# isochrone frame
isochrone_frame$time_groups <-  with(isochrone_frame,
                                   ifelse(avg_time < 15, "15",
                                   ifelse(avg_time < 30, "30",
                                   ifelse(avg_time < 45, "45",
                                   ifelse(avg_time < 60, "60",
                                   ifelse(avg_time < 75, "75",
                                   ifelse(avg_time < 90, "90", "90+")))))))

isochrone_frame <- isochrone_frame[, c(1,2,4)]
head(isochrone_frame)

Import the dissemination block shape file

canada_shape <- st_read("../data/census2016_DBS_shp/DB_Van_CMA/DB_Van_CMA.shp", stringsAsFactors = FALSE)
Reading layer `DB_Van_CMA' from data source `C:\Users\Luka\Documents\GitHub\Capstone-Project\data\census2016_DBS_shp\DB_Van_CMA\DB_Van_CMA.shp' using driver `ESRI Shapefile'
Simple feature collection with 15197 features and 27 fields
Geometry type: MULTIPOLYGON
Dimension:     XY
Bounding box:  xmin: 4001643 ymin: 1957237 xmax: 4068119 ymax: 2032663
Projected CRS: PCS_Lambert_Conformal_Conic
# select a greater metropolitan area
metropolitan_area <- "Vancouver"

# filter columns and rows
vancouver_shape <- data.frame(canada_shape[which(canada_shape$CMANAME == metropolitan_area), c(1, 28)])

# id to factor
vancouver_shape$DBUID <- as.factor(vancouver_shape$DBUID)

paste('Rows = ', nrow(vancouver_shape))
[1] "Rows =  15197"
head(vancouver_shape)

2) Score Computation

Notes:

# import score functions
source('Score Modeling/Updated_Score_Functions.R')

score_list <- list()
i <- 1
for (n in c(1, 2, 3, 4)) {
  
  if (n == 4) { n <- NULL }
  score <- sum_score_fxn(ttm, nearest_n = n, weight = FALSE, log_normalize_score = FALSE)
  score_list[[i]] <- score
  i <- i+1
}
`summarise()` has grouped output by 'fromId'. You can override using the `.groups` argument.
`summarise()` has grouped output by 'fromId'. You can override using the `.groups` argument.
`summarise()` has grouped output by 'fromId', 'type'. You can override using the `.groups` argument.
`summarise()` has grouped output by 'fromId'. You can override using the `.groups` argument.
`summarise()` has grouped output by 'fromId', 'type'. You can override using the `.groups` argument.
`summarise()` has grouped output by 'fromId'. You can override using the `.groups` argument.
`summarise()` has grouped output by 'fromId'. You can override using the `.groups` argument.
scores_long <- rbindlist(score_list) %>% arrange(fromId, nearest_n)

scores_long
NA

3) Dataset Wrangling Part II (NA Insertion)

Each origin(fromId) should have x different scores based on the possible equation below:

In the current file 1 weight options * 4 amenity options * 4 nearest options = 16

In the future 2 weight options * 4 amenity options * 4 nearest options = 32

Many zones however don’t have any routes, such as provincial parks which aren’t linked to via transit. To visualize them in the data we need to attribute these cases an NA value. Since these rows are missing we’ll use the following code to re-add those NA values.

# target zone count
x <- 16

# check for the inconsistency in values
counted <- scores_long %>% group_by(fromId) %>% summarize(n = n()) %>% arrange(n) 

# check for current unique counts to make sure it's multiples of the type number
paste('Unique counts: '); unique(counted$n)
[1] "Unique counts: "
[1]  4  8 16
# get expected rows by multiplying unique IDs by x
n_fromIds <- uniqueN(scores_long$fromId)
N <- n_fromIds*x

paste(glue('{nrow(scores_long)} of {N} rows filled ({round((nrow(scores_long) / N)*100, 2)}%)'))
[1] "229128 of 229488 rows filled (99.84%)"
paste(N - nrow(scores_long), 'to fill.')
[1] "360 to fill."

# function for NA grid expansion 
NA_grid_maker <- function(id, df, isochrone = FALSE) {
  
  all_amenities <- as.character(unique(df$type))
  
  # get missing amenities by indexing the fromId and keeping only unique types
  missing_amenities <- setdiff(all_amenities, unique(df$type[df$fromId == id]))
  
  if (isochrone == FALSE) {
    # create NA rows to append via expand.grid (creates a row for every factor combination)
    NA_rows <- expand.grid('fromId' = id,
                         'type' = missing_amenities,
                         'weight' = as.character(unique(df$weight)),
                         'nearest_n' = as.character(unique(df$nearest_n)),
                         'score' = NA, 
                         stringsAsFactors = TRUE)
  } else {
    # create NA rows to append via expand.grid (creates a row for every factor combination)
    NA_rows <- expand.grid('fromId' = id,
                         'type' = missing_amenities,
                         'time_groups' = NA, 
                         stringsAsFactors = TRUE)
  }
  
  NA_rows
}

# function for filling table with NA values
NA_table_filler <- function(df, custom_idx = NULL, isochrone = FALSE) {
  
  # count each fromId occurence
  fromId_counts <- df %>% group_by(fromId) %>% mutate(n = n())
  
  if (is.null(custom_idx)) {
    # create a fromId array using Ids that don't meet the [x] count requirement
    id_arr <- array(unique(fromId_counts[fromId_counts$n < x, ]$fromId))
  } else {
    id_arr <- custom_idx
  }
  
  # get rows
  filler_rows <- rbindlist(apply(id_arr, MARGIN = 1, FUN = NA_grid_maker, df = df, isochrone = isochrone))

  # append and order
  df <- rbindlist(list(df, filler_rows), use.names = TRUE) 
  
  if (isochrone == FALSE) {
    df <- df %>% arrange(fromId, type, nearest_n, weight)
  } else {
    df <- df %>% arrange(fromId, type)
  }
  
  df
}


partial_filled_scores_long <- NA_table_filler(scores_long)

Although, recall that not all origins were used in the score computation, so we need to add those NA values as well.

Now lets add population data to the scores and isochrone frame


## Export checkpoint
write.csv(filled_scores_long, 'June2_scores_long.csv', row.names = FALSE)
write.csv(filled_isochrone_frame, 'June2_isochrone_frame.csv', row.names = FALSE)

4) Interactive Visualization

# join factor and geometry data 
scores_viz_frame <- left_join(vancouver_shape, filled_scores_long, by = c('DBUID' = 'fromId'))
isochrone_viz_frame <- left_join(vancouver_shape, filled_isochrone_frame, by = c('DBUID' = 'fromId'))

# convert back to sf object
scores_viz_frame_sf <- st_as_sf(scores_viz_frame)
scores_viz_frame_st <- st_transform(scores_viz_frame_sf, crs = 4326)

isochrone_viz_frame_sf <- st_as_sf(isochrone_viz_frame)
isochrone_viz_frame_st <- st_transform(isochrone_viz_frame_sf, crs = 4326)
sort(unique(isochrone_viz_frame_st$time_groups))
[1] "15"  "30"  "45"  "60"  "75"  "90"  "90+"
map_maker_isochrone(isochrone_viz_frame_st, 'gallery')
[1] "Current Map:"
Gallery Transit Isochrone

5) Map HTML Exports


for (amenity in unique(scores_viz_frame_st$type)) { 
  
  # 4 isochrone maps
  map_maker_isochrone(data = isochrone_viz_frame_st, amenity)

  for (weight in unique(scores_viz_frame_st$weight)) {
    for (nearest_n in unique(scores_viz_frame_st$nearest_n)) {
      
      # 16 others
      map_maker_scores(data = scores_viz_frame_st, amenity, weight, nearest_n)
      
    }
  }
}
LS0tDQp0aXRsZTogIlRyYXZlbCBUaW1lIE1hdHJpeCB0byBNYXBzIg0Kb3V0cHV0OiBodG1sX25vdGVib29rDQotLS0NCg0KIyMgTm90ZWJvb2sgUHVycG9zZQ0KDQpUaGlzIG5vdGVib29rIHNlcnZlcyB0byBzdW1tYXJpemUgdGhlIGVudGlyZSB2aXN1YWxpemF0aW9uIHByb2Nlc3MgZ29pbmcgZnJvbSANCnRoZSB0cmF2ZWwgdGltZSBtYXRyaXggdG8gdGhlIHZpc3VhbGl6YXRpb25zLiBUaGF0IGludm9sdmVzIHRoZSBmb2xsb3dpbmcgDQpzZWN0aW9uczoNCg0KMSkgVHJhdmVsIFRpbWUgTWF0cml4IFdyYW5nbGluZw0KMikgU2NvcmUgQ29tcHV0YXRpb24NCjMpIElzb2Nocm9uZSBDb21wdXRhdGlvbg0KNCkgRGF0YXNldCBXcmFuZ2xpbmcgUGFydCBJSSAoTkEgSW5zZXJ0aW9uKQ0KNSkgSW50ZXJhY3RpdmUgVmlzdWFsaXphdGlvbg0KNikgTWFwIEhUTUwgRXhwb3J0cw0KDQojIyAwKSBVc2VmdWwgTGlicmFyaWVzDQoNCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0UsIGluY2x1ZGU9VFJVRX0NCiMgd3JhbmdsaW5nL2NvbnZlbmllbmNlDQpsaWJyYXJ5KHRpZHl2ZXJzZSkNCmxpYnJhcnkoZ2x1ZSkNCmxpYnJhcnkoc3RyaW5ncikNCmxpYnJhcnkoc2YpDQpsaWJyYXJ5KGRhdGEudGFibGUpDQoNCiMgdmlzdWFsaXphdGlvbg0KbGlicmFyeShsZWFmbGV0KQ0KbGlicmFyeShtYXB2aWV3KTsgbWFwdmlld09wdGlvbnMocGxhdGZvcm0gPSAnbGVhZmdsJykNCiNsaWJyYXJ5KGdncGxvdDIpDQojbGlicmFyeShSQ29sb3JCcmV3ZXIpDQojbGlicmFyeShzY2FsZXMpDQojbGlicmFyeShsYXR0aWNlKQ0KDQojIEZvciBwcmV0dHkga25pdHRpbmcNCmxpYnJhcnkobGVtb24pDQprbml0X3ByaW50LmRhdGEuZnJhbWUgPC0gbGVtb25fcHJpbnQNCmtuaXRfcHJpbnQudGJsIDwtIGxlbW9uX3ByaW50DQprbml0X3ByaW50LnN1bW1hcnkgPC0gbGVtb25fcHJpbnQNCmBgYA0KDQoNCg0KDQoNCiMjIDEpIERhdGEgV3JhbmdsaW5nDQoNCiMjIyBJbXBvcnQgYWxsIGRpc3NlbWluYXRpb24gYmxvY2sgZGF0YQ0KYGBge3J9DQojIGltcG9ydCBkaXNzZW1pbmF0aW9uIGJsb2NrcyBhbmQga2VlcCBpZCBhbmQgcG9wIGNvbHVtbnMNCm9yaWdpbnMgPC0gZnJlYWQoZmlsZS5wYXRoKCIuLi9kYXRhL2NsZWFuIiwgInZhbmNvdXZlcl9kYi5jc3YiKSlbLCAuKGlkLCBwb3ApXQ0Kb3JpZ2lucyRwb3AgPC0gc3RyX3JlcGxhY2VfYWxsKG9yaWdpbnMkcG9wLCAnLCcsICcnKQ0KDQojIGNoYW5nZSBjb2wgdHlwZXMNCm9yaWdpbnMkcG9wIDwtIGFzLm51bWVyaWMob3JpZ2lucyRwb3ApICANCm9yaWdpbnMkaWQgPC0gYXMuZmFjdG9yKG9yaWdpbnMkaWQpICANCg0Kbl9vcmlnaW5zIDwtIG5yb3cob3JpZ2lucykNCnBhc3RlKCdPcmlnaW4gUm93czogJywgbl9vcmlnaW5zKQ0KDQojIFBlZWsNCmhlYWQob3JpZ2lucykNCmBgYA0KDQoNCiMjIyBJbXBvcnQgYWxsIGFtZW5pdHkgZGF0YQ0KYGBge3J9DQojIGltcG9ydCBhbWVuaXRpZXMgKEN1bHR1cmFsL0FydCBmYWNpbGl0aWVzKQ0KZGVzdGluYXRpb25zIDwtIGZyZWFkKGZpbGUucGF0aCgiLi4vZGF0YS9jbGVhbiIsICJ2YW5jb3V2ZXJfZmFjaWxpdGllc18yLmNzdiIpKQ0KDQojIHNlZSBzdW1tYXJ5IGNvdW50cyBvZiBlYWNoIGFtZW5pdHkNCmRlc3RpbmF0aW9ucyAlPiUgZ3JvdXBfYnkodHlwZSkgJT4lIHN1bW1hcmlzZShjb3VudCA9IG4oKSkgJT4lIGFycmFuZ2UoZGVzYyhjb3VudCkpDQoNCiMgY2xlYW4gYW1lbml0aWVzIC8gZmlsdGVyIHR5cGVzIHRvIGtlZXAgNCBtb3N0IGZyZXF1ZW50IGFtZW5pdGllcw0KdGFyZ2V0X2FtZW5pdGllcyA8LSBjKCdnYWxsZXJ5JywgJ211c2V1bScsICdsaWJyYXJ5IG9yIGFyY2hpdmVzJywgJ3RoZWF0cmUvcGVyZm9ybWFuY2UgYW5kIGNvbmNlcnQgaGFsbCcpDQpkZXN0aW5hdGlvbnMgPC0gZGVzdGluYXRpb25zICU+JSBmaWx0ZXIodHlwZSAlaW4lIHRhcmdldF9hbWVuaXRpZXMpDQojIGtlZXAgb25seSBpZCBhbmQgdHlwZSBjb2x1bW5zDQpkZXN0aW5hdGlvbnMgPC0gZGVzdGluYXRpb25zWyAsIC4oaWQsIHR5cGUpXQ0KIyBjaGFuZ2UgY29sIHR5cGVzDQpkZXN0aW5hdGlvbnMkdHlwZSA8LSBhcy5mYWN0b3IoZGVzdGluYXRpb25zJHR5cGUpDQpkZXN0aW5hdGlvbnMkaWQgPC0gYXMuZmFjdG9yKGRlc3RpbmF0aW9ucyRpZCkgIA0KDQpuX2FtZW5pdGllcyA8LSBucm93KGRlc3RpbmF0aW9ucykNCnBhc3RlKCdEZXN0aW5hdGlvbnM6ICcsIG5fYW1lbml0aWVzKQ0KaGVhZChkZXN0aW5hdGlvbnMpDQpgYGANCg0KDQojIyMgSW1wb3J0IHRoZSB0cmF2ZWwgdGltZSBtYXRyaXgNCmBgYHtyIGthYmxlLm9wdHM9bGlzdChjYXB0aW9uPSdTdW1tYXJ5IFRhYmxlJyl9DQojIGltcG9ydCB0cmF2ZWwgdGltZSBtYXRyaXgNCnR0bSA8LSBmcmVhZChmaWxlLnBhdGgoJy4uL2RhdGEvY2xlYW4nLCAndHRtLmNzdicpKQ0KDQojIGNvbnZlcnQgSWRzIHRvICBmYWN0b3INCnR0bSRmcm9tSWQgPC0gYXMuZmFjdG9yKHR0bSRmcm9tSWQpDQp0dG0kdG9JZCA8LSBhcy5mYWN0b3IodHRtJHRvSWQpDQoNCiMjIFJlcGxhY2UgdHJhdmVsIHRpbWVzIGxlc3MgdGhhbiAxIG1pbnV0ZSB0byAxIG1pbnV0ZQ0KIyBUaGlzIGlzIGRvbmUgdG8gcHJldmVudCBpbmZpbml0eSB2YWx1ZXMgaW4gdGhlIHNjb3Jpbmcgc2luY2UNCiMgMSBtaW51dGUgaXMgc3RpbGwgYSByZWFzb25hYmxlIHRpbWUgZm9yIHRyaXBzIGluIHRoZSAwIC0gMSBtaW4gcmFuZ2UuDQp0dG0kYXZnX3RpbWUgPC0gcG1heCh0dG0kYXZnX3RpbWUsIDEpDQoNCiMgYWRkIGFtZW5pdHkgdHlwZXMNCiMgdXNlIGxlZnQgam9pbiBzaW5jZSB3ZSBvbmx5IGNhcmUgdG8ga2VlcCBleGlzdGluZyBhbWVuaXRpZXMgaW4gdGhlIHR0bQ0KdHRtIDwtICBsZWZ0X2pvaW4odHRtLCBkZXN0aW5hdGlvbnMsIGJ5ID0gYygndG9JZCcgPSAnaWQnKSkNCg0KIyBob3cgbWFueSBvcmlnaW5zIGFjdHVhbGx5IGhhdmUgdHJhbnNpdCBhY2Nlc3NpYmlsaXR5DQpwYXN0ZSgnT3JpZ2lucyBjb25zaWRlcmVkOicsIHJvdW5kKChsZW5ndGgodW5pcXVlKHR0bSRmcm9tSWQpKS9uX29yaWdpbnMpKjEwMCwgMiksICclJykNCnBhc3RlKCdEZXN0aW5hdGlvbnMgY29uc2lkZXJlZDonLCByb3VuZChsZW5ndGgodW5pcXVlKHR0bSR0b0lkKSkvbl9hbWVuaXRpZXMqMTAwLCAyKSwgJyUnKQ0KcGFzdGUoJ1Jvd3MgPSAnLCBucm93KHR0bSkpDQoNCiMgcGVlaw0KaGVhZCh0dG0pDQpgYGANCg0KDQoNCiMjIyBJbXBvcnQgYW1lbml0eSB3ZWlnaHRzIGJ1dCB0aGV5IHN0aWxsIGhhdmUgaXNzdWVzOg0KDQpUaGVyZSBhcmUgKipkdXBsaWNhdGVzKiogb24gSURzIDQxODAgYW5kIDQxODEuDQoNCkFkZGl0aW9uYWxseSwgb2YgYWxsIGFtZW5pdHkgSURzIGluIHRoZSB0cmF2ZWwgdGltZSBtYXRyaXgsIG5vdCBhbGwgYXJlIGZvdW5kDQppbiB0aGUgd2VpZ2h0IHNldC4NCg0KYGBge3J9DQojIGltcG9ydCBhbWVuaXR5IHdlaWdodHMNCmFtZW5pdHlfd3RzIDwtIHJlYWQuY3N2KCcuLi9kYXRhL2FtZW5pdHlfd2VpZ2h0cy9hbWVuaXR5X3d0cy5jc3YnKQ0KDQojIGNsZWFuIHdlaWdodHMNCmFtZW5pdHlfd3RzIDwtIGFtZW5pdHlfd3RzWywgYygnaWQnLCAnSW5kZXgnKV0NCm5hbWVzKGFtZW5pdHlfd3RzKSA8LSBjKCdpZCcsICd3ZWlnaHQnKQ0KYW1lbml0eV93dHMkaWQgPC0gIGFzLmZhY3RvcihhbWVuaXR5X3d0cyRpZCkNCg0KIyBzZWUgd2VpZ2h0IGRpc3RyaWJ1dGlvbg0KI3Bsb3QoZGVuc2l0eShhbWVuaXR5X3d0cyR3ZWlnaHQpLCBtYWluID0gJ0FtZW5pdHkgUG9wdWxhcml0eSBEaXN0cmlidXRpb24nKQ0KDQphbWVuaXR5X3d0cyAlPiUgZ3JvdXBfYnkoaWQpICU+JSBzdW1tYXJpemUobiA9IG4oKSkgJT4lIGFycmFuZ2UoZGVzYyhuKSkNCg0KIyBhcmUgYWxsIHRoZSB0dG0gYW1lbml0eSBJRHMgaW4gdGhlIHdlaWdodGVkIElEcyBzZXQ/DQpwYXN0ZSgnQXJlIGFsbCB0aGUgdHRtIGFtZW5pdHkgSURzIGluIHRoZSB3ZWlnaHRlZCBJRHMgc2V0PyAobmVlZHMgdG8gYmUgdHJ1ZSBmb3IgdGhlIGpvaW4gdG8gd29yayknKQ0KYWxsKHVuaXF1ZSh0dG0kdG9JZCkgJWluJSB1bmlxdWUoYW1lbml0eV93dHMkaWQpKQ0KDQoNCiMgam9pbiB3aGVuIHdlaWdodCBpbmRleCBpcyBjb3JyZWN0ZWQNCiNkZXN0aW5hdGlvbnMgPC0gbGVmdF9qb2luKGRlc3RpbmF0aW9ucywgYW1lbml0eV93dHMsIGJ5ID0gYygnaWQnID0gJ2lkJykpDQoNCmBgYA0KDQoNCiMjIyBJc29jaHJvbmUgRnJhbWUgYW5kIFR5cGVzDQpgYGB7cn0NCg0KIyBvbmx5IGNvbnNpZGVyIHRoZSBuZWFyZXN0IGFtZW5pdHkNCmlzb2Nocm9uZV9mcmFtZSA8LSB0dG0gJT4lDQogIGdyb3VwX2J5KGZyb21JZCwgdHlwZSkgJT4lDQogIHN1bW1hcmlzZShhdmdfdGltZSA9IG1pbihhdmdfdGltZSkpDQoNCiMgaXNvY2hyb25lIGZyYW1lDQppc29jaHJvbmVfZnJhbWUkdGltZV9ncm91cHMgPC0gIHdpdGgoaXNvY2hyb25lX2ZyYW1lLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpZmVsc2UoYXZnX3RpbWUgPCAxNSwgIjE1IiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaWZlbHNlKGF2Z190aW1lIDwgMzAsICIzMCIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGlmZWxzZShhdmdfdGltZSA8IDQ1LCAiNDUiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpZmVsc2UoYXZnX3RpbWUgPCA2MCwgIjYwIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaWZlbHNlKGF2Z190aW1lIDwgNzUsICI3NSIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGlmZWxzZShhdmdfdGltZSA8IDkwLCAiOTAiLCAiOTArIikpKSkpKSkNCg0KaXNvY2hyb25lX2ZyYW1lIDwtIGlzb2Nocm9uZV9mcmFtZVssIGMoMSwyLDQpXQ0KaGVhZChpc29jaHJvbmVfZnJhbWUpDQpgYGANCmBgYHtyfQ0KDQojIHNlZSBob3cgbWFueSB0cmlwcyBpbiBlYWNoIHRpbWVfZ3JvdXANCnR0bV9mdWxsICU+JSBncm91cF9ieSh0aW1lX2dyb3VwKSAlPiUgc3VtbWFyaXNlKG4gPSBuKCkpIC0+IHRfZ3JvdXBzDQoNCnBsb3QodF9ncm91cHMkbiwgdHlwZSA9ICdiJykNCmBgYA0KDQoNCiMjIyBJbXBvcnQgdGhlIGRpc3NlbWluYXRpb24gYmxvY2sgc2hhcGUgZmlsZQ0KYGBge3J9DQpjYW5hZGFfc2hhcGUgPC0gc3RfcmVhZCgiLi4vZGF0YS9jZW5zdXMyMDE2X0RCU19zaHAvREJfVmFuX0NNQS9EQl9WYW5fQ01BLnNocCIsIHN0cmluZ3NBc0ZhY3RvcnMgPSBGQUxTRSkNCg0KIyBzZWxlY3QgYSBncmVhdGVyIG1ldHJvcG9saXRhbiBhcmVhDQptZXRyb3BvbGl0YW5fYXJlYSA8LSAiVmFuY291dmVyIg0KDQojIGZpbHRlciBjb2x1bW5zIGFuZCByb3dzDQp2YW5jb3V2ZXJfc2hhcGUgPC0gZGF0YS5mcmFtZShjYW5hZGFfc2hhcGVbd2hpY2goY2FuYWRhX3NoYXBlJENNQU5BTUUgPT0gbWV0cm9wb2xpdGFuX2FyZWEpLCBjKDEsIDI4KV0pDQoNCiMgaWQgdG8gZmFjdG9yDQp2YW5jb3V2ZXJfc2hhcGUkREJVSUQgPC0gYXMuZmFjdG9yKHZhbmNvdXZlcl9zaGFwZSREQlVJRCkNCg0KcGFzdGUoJ1Jvd3MgPSAnLCBucm93KHZhbmNvdXZlcl9zaGFwZSkpDQpoZWFkKHZhbmNvdXZlcl9zaGFwZSkNCmBgYA0KDQoNCg0KDQoNCg0KIyMgMikgU2NvcmUgQ29tcHV0YXRpb24NCg0KTm90ZXM6IA0KDQotIFdlIGRvbid0IHNjYWxlIHRoZSBkYXRhIGJlZm9yZSBzY29yZSBjb21wdXRhdGlvbiBiZWNhdXNlIHdlIGNhcmUgYWJvdXQgdGhlDQpzY2FsZSBvZiB0aW1lIGFuZCBzdGFuZGFyZCBkZXZpYXRpb24uIFdlIHNob3VsZCB1c2UgdGhlaXIgdmFsdWVzIGFzIGlzLCANCm90aGVyd2lzZSBzY2FsaW5nIHdpbGwgZ2l2ZSB0aGVtIGVxdWFsIHdlaWdoaW5nIGFnYWluIHdoZW4gdGhpcyBpcyBhIGJhZA0KYXNzdW1wdGlvbi4NCi0gTG9nIG5vcm1hbGl6aW5nIHRoZSBzY29yZSBpc24ndCBpbXBvcnRhbnQgYXMgdGhlIHNjb3JlIHZpc3VhbGl6YXRpb24gZGVwZW5kcyANCm9uIHRoZSBxdWFudGlsZXMgdGFrZW4gZnJvbSB0aGUgZGlzdHJpYnV0aW9uIG9mIHNjb3Jlcy4gU2luY2UgbG9nIG9ubHkgc2hpZnRzIA0KdmFsdWVzLCB0aGUgdmlzdWFsaXphdGlvbnMgd2lsbCBiZSBpZGVudGljYWwuDQoNCg0KDQpgYGB7cn0NCiMgaW1wb3J0IHNjb3JlIGZ1bmN0aW9ucw0Kc291cmNlKCdTY29yZSBNb2RlbGluZy9VcGRhdGVkX1Njb3JlX0Z1bmN0aW9ucy5SJykNCg0Kc2NvcmVfbGlzdCA8LSBsaXN0KCkNCmkgPC0gMQ0KDQpmb3IgKG4gaW4gYygxLCAyLCAzLCA0KSkgew0KICANCiAgaWYgKG4gPT0gNCkgeyBuIDwtIE5VTEwgfQ0KICBzY29yZSA8LSBzdW1fc2NvcmVfZnhuKHR0bSwgbmVhcmVzdF9uID0gbiwgd2VpZ2h0ID0gRkFMU0UsIGxvZ19ub3JtYWxpemVfc2NvcmUgPSBGQUxTRSkNCiAgc2NvcmVfbGlzdFtbaV1dIDwtIHNjb3JlDQogIGkgPC0gaSsxDQogIA0KfQ0KDQpzY29yZXNfbG9uZyA8LSByYmluZGxpc3Qoc2NvcmVfbGlzdCkgJT4lIGFycmFuZ2UoZnJvbUlkLCBuZWFyZXN0X24pDQoNCnNjb3Jlc19sb25nDQoNCmBgYA0KDQoNCg0KIyMgMykgRGF0YXNldCBXcmFuZ2xpbmcgUGFydCBJSSAoTkEgSW5zZXJ0aW9uKQ0KDQpFYWNoIG9yaWdpbihmcm9tSWQpIHNob3VsZCBoYXZlIHggZGlmZmVyZW50IHNjb3JlcyBiYXNlZCBvbiB0aGUgcG9zc2libGUgDQplcXVhdGlvbiBiZWxvdzoNCg0KKkluIHRoZSBjdXJyZW50IGZpbGUqDQoxIHdlaWdodCBvcHRpb25zICogNCBhbWVuaXR5IG9wdGlvbnMgKiA0IG5lYXJlc3Qgb3B0aW9ucyA9IDE2DQoNCipJbiB0aGUgZnV0dXJlKg0KMiB3ZWlnaHQgb3B0aW9ucyAqIDQgYW1lbml0eSBvcHRpb25zICogNCBuZWFyZXN0IG9wdGlvbnMgPSAzMg0KDQoNCk1hbnkgem9uZXMgaG93ZXZlciBkb24ndCBoYXZlIGFueSByb3V0ZXMsIHN1Y2ggYXMgcHJvdmluY2lhbCBwYXJrcyB3aGljaCBhcmVuJ3QNCmxpbmtlZCB0byB2aWEgdHJhbnNpdC4gVG8gdmlzdWFsaXplIHRoZW0gaW4gdGhlIGRhdGEgd2UgbmVlZCB0byBhdHRyaWJ1dGUgdGhlc2UNCmNhc2VzIGFuIE5BIHZhbHVlLiBTaW5jZSB0aGVzZSByb3dzIGFyZSBtaXNzaW5nIHdlJ2xsIHVzZSB0aGUgZm9sbG93aW5nIGNvZGUNCnRvIHJlLWFkZCB0aG9zZSBOQSB2YWx1ZXMuDQoNCmBgYHtyfQ0KIyB0YXJnZXQgem9uZSBjb3VudA0KeCA8LSAxNg0KDQojIGNoZWNrIGZvciB0aGUgaW5jb25zaXN0ZW5jeSBpbiB2YWx1ZXMNCmNvdW50ZWQgPC0gc2NvcmVzX2xvbmcgJT4lIGdyb3VwX2J5KGZyb21JZCkgJT4lIHN1bW1hcml6ZShuID0gbigpKSAlPiUgYXJyYW5nZShuKSANCg0KIyBjaGVjayBmb3IgY3VycmVudCB1bmlxdWUgY291bnRzIHRvIG1ha2Ugc3VyZSBpdCdzIG11bHRpcGxlcyBvZiB0aGUgdHlwZSBudW1iZXINCnBhc3RlKCdVbmlxdWUgY291bnRzOiAnKTsgdW5pcXVlKGNvdW50ZWQkbikNCg0KIyBnZXQgZXhwZWN0ZWQgcm93cyBieSBtdWx0aXBseWluZyB1bmlxdWUgSURzIGJ5IHgNCm5fZnJvbUlkcyA8LSB1bmlxdWVOKHNjb3Jlc19sb25nJGZyb21JZCkNCk4gPC0gbl9mcm9tSWRzKngNCg0KcGFzdGUoZ2x1ZSgne25yb3coc2NvcmVzX2xvbmcpfSBvZiB7Tn0gcm93cyBmaWxsZWQgKHtyb3VuZCgobnJvdyhzY29yZXNfbG9uZykgLyBOKSoxMDAsIDIpfSUpJykpDQpwYXN0ZShOIC0gbnJvdyhzY29yZXNfbG9uZyksICd0byBmaWxsLicpDQpgYGANCg0KYGBge3J9DQoNCiMgZnVuY3Rpb24gZm9yIE5BIGdyaWQgZXhwYW5zaW9uIA0KTkFfZ3JpZF9tYWtlciA8LSBmdW5jdGlvbihpZCwgZGYsIGlzb2Nocm9uZSA9IEZBTFNFKSB7DQogIA0KICBhbGxfYW1lbml0aWVzIDwtIGFzLmNoYXJhY3Rlcih1bmlxdWUoZGYkdHlwZSkpDQogIA0KICAjIGdldCBtaXNzaW5nIGFtZW5pdGllcyBieSBpbmRleGluZyB0aGUgZnJvbUlkIGFuZCBrZWVwaW5nIG9ubHkgdW5pcXVlIHR5cGVzDQogIG1pc3NpbmdfYW1lbml0aWVzIDwtIHNldGRpZmYoYWxsX2FtZW5pdGllcywgdW5pcXVlKGRmJHR5cGVbZGYkZnJvbUlkID09IGlkXSkpDQogIA0KICBpZiAoaXNvY2hyb25lID09IEZBTFNFKSB7DQogICAgIyBjcmVhdGUgTkEgcm93cyB0byBhcHBlbmQgdmlhIGV4cGFuZC5ncmlkIChjcmVhdGVzIGEgcm93IGZvciBldmVyeSBmYWN0b3IgY29tYmluYXRpb24pDQogICAgTkFfcm93cyA8LSBleHBhbmQuZ3JpZCgnZnJvbUlkJyA9IGlkLA0KICAgICAgICAgICAgICAgICAgICAgICAgICd0eXBlJyA9IG1pc3NpbmdfYW1lbml0aWVzLA0KICAgICAgICAgICAgICAgICAgICAgICAgICd3ZWlnaHQnID0gYXMuY2hhcmFjdGVyKHVuaXF1ZShkZiR3ZWlnaHQpKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAnbmVhcmVzdF9uJyA9IGFzLmNoYXJhY3Rlcih1bmlxdWUoZGYkbmVhcmVzdF9uKSksDQogICAgICAgICAgICAgICAgICAgICAgICAgJ3Njb3JlJyA9IE5BLCANCiAgICAgICAgICAgICAgICAgICAgICAgICBzdHJpbmdzQXNGYWN0b3JzID0gVFJVRSkNCiAgfSBlbHNlIHsNCiAgICAjIGNyZWF0ZSBOQSByb3dzIHRvIGFwcGVuZCB2aWEgZXhwYW5kLmdyaWQgKGNyZWF0ZXMgYSByb3cgZm9yIGV2ZXJ5IGZhY3RvciBjb21iaW5hdGlvbikNCiAgICBOQV9yb3dzIDwtIGV4cGFuZC5ncmlkKCdmcm9tSWQnID0gaWQsDQogICAgICAgICAgICAgICAgICAgICAgICAgJ3R5cGUnID0gbWlzc2luZ19hbWVuaXRpZXMsDQogICAgICAgICAgICAgICAgICAgICAgICAgJ3RpbWVfZ3JvdXBzJyA9IE5BLCANCiAgICAgICAgICAgICAgICAgICAgICAgICBzdHJpbmdzQXNGYWN0b3JzID0gVFJVRSkNCiAgfQ0KICANCiAgTkFfcm93cw0KfQ0KDQojIGZ1bmN0aW9uIGZvciBmaWxsaW5nIHRhYmxlIHdpdGggTkEgdmFsdWVzDQpOQV90YWJsZV9maWxsZXIgPC0gZnVuY3Rpb24oZGYsIGN1c3RvbV9pZHggPSBOVUxMLCBpc29jaHJvbmUgPSBGQUxTRSkgew0KICANCiAgIyBjb3VudCBlYWNoIGZyb21JZCBvY2N1cmVuY2UNCiAgZnJvbUlkX2NvdW50cyA8LSBkZiAlPiUgZ3JvdXBfYnkoZnJvbUlkKSAlPiUgbXV0YXRlKG4gPSBuKCkpDQogIA0KICBpZiAoaXMubnVsbChjdXN0b21faWR4KSkgew0KICAgICMgY3JlYXRlIGEgZnJvbUlkIGFycmF5IHVzaW5nIElkcyB0aGF0IGRvbid0IG1lZXQgdGhlIFt4XSBjb3VudCByZXF1aXJlbWVudA0KICAgIGlkX2FyciA8LSBhcnJheSh1bmlxdWUoZnJvbUlkX2NvdW50c1tmcm9tSWRfY291bnRzJG4gPCB4LCBdJGZyb21JZCkpDQogIH0gZWxzZSB7DQogICAgaWRfYXJyIDwtIGN1c3RvbV9pZHgNCiAgfQ0KICANCiAgIyBnZXQgcm93cw0KICBmaWxsZXJfcm93cyA8LSByYmluZGxpc3QoYXBwbHkoaWRfYXJyLCBNQVJHSU4gPSAxLCBGVU4gPSBOQV9ncmlkX21ha2VyLCBkZiA9IGRmLCBpc29jaHJvbmUgPSBpc29jaHJvbmUpKQ0KDQogICMgYXBwZW5kIGFuZCBvcmRlcg0KICBkZiA8LSByYmluZGxpc3QobGlzdChkZiwgZmlsbGVyX3Jvd3MpLCB1c2UubmFtZXMgPSBUUlVFKSANCiAgDQogIGlmIChpc29jaHJvbmUgPT0gRkFMU0UpIHsNCiAgICBkZiA8LSBkZiAlPiUgYXJyYW5nZShmcm9tSWQsIHR5cGUsIG5lYXJlc3Rfbiwgd2VpZ2h0KQ0KICB9IGVsc2Ugew0KICAgIGRmIDwtIGRmICU+JSBhcnJhbmdlKGZyb21JZCwgdHlwZSkNCiAgfQ0KICANCiAgZGYNCn0NCg0KDQpwYXJ0aWFsX2ZpbGxlZF9zY29yZXNfbG9uZyA8LSBOQV90YWJsZV9maWxsZXIoc2NvcmVzX2xvbmcpDQoNCg0KDQpgYGANCg0KQWx0aG91Z2gsIHJlY2FsbCB0aGF0IG5vdCBhbGwgb3JpZ2lucyB3ZXJlIHVzZWQgaW4gdGhlIHNjb3JlIGNvbXB1dGF0aW9uLCANCnNvIHdlIG5lZWQgdG8gYWRkIHRob3NlIE5BIHZhbHVlcyBhcyB3ZWxsLg0KDQpgYGB7cn0NCm1pc3NpbmdfYmxvY2tzIDwtIGFycmF5KHNldGRpZmYob3JpZ2lucyRpZCwgcGFydGlhbF9maWxsZWRfc2NvcmVzX2xvbmckZnJvbUlkKSkNCg0KDQojIGZpbGwgcGFydGlhbF9maWxsZWRfc2NvcmVzX2xvbmcNCmZpbGxlZF9zY29yZXNfbG9uZyA8LSBOQV90YWJsZV9maWxsZXIocGFydGlhbF9maWxsZWRfc2NvcmVzX2xvbmcsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGN1c3RvbV9pZHggPSBtaXNzaW5nX2Jsb2NrcykNCg0KIyBmaWxsIGlzb2Nocm9uZSBmcmFtZQ0KZmlsbGVkX2lzb2Nocm9uZV9mcmFtZSA8LSBOQV90YWJsZV9maWxsZXIoaXNvY2hyb25lX2ZyYW1lLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY3VzdG9tX2lkeCA9IG1pc3NpbmdfYmxvY2tzLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaXNvY2hyb25lID0gVFJVRSkNCmBgYA0KDQoNCk5vdyBsZXRzIGFkZCBwb3B1bGF0aW9uIGRhdGEgdG8gdGhlIHNjb3JlcyBhbmQgaXNvY2hyb25lIGZyYW1lDQoNCmBgYHtyfQ0KIyByaWdodCBqb2luIHdpdGggb3JpZ2lucyB0byBpbmNsdWRlIG9yaWdpbnMgd2l0aG91dCB0cmFuc2l0IGFjY2Vzcw0KDQpmaWxsZWRfc2NvcmVzX2xvbmcgPC0gcmlnaHRfam9pbihmaWxsZWRfc2NvcmVzX2xvbmcsIG9yaWdpbnMsIGJ5ID0gYygnZnJvbUlkJyA9ICdpZCcpKQ0KZmlsbGVkX2lzb2Nocm9uZV9mcmFtZSA8LSByaWdodF9qb2luKGlzb2Nocm9uZV9mcmFtZSwgb3JpZ2lucywgYnkgPSBjKCdmcm9tSWQnID0gJ2lkJykpDQoNCmBgYA0KDQpgYGB7cn0NCg0KIyMgRXhwb3J0IGNoZWNrcG9pbnQNCndyaXRlLmNzdihmaWxsZWRfc2NvcmVzX2xvbmcsICdKdW5lMl9zY29yZXNfbG9uZy5jc3YnLCByb3cubmFtZXMgPSBGQUxTRSkNCndyaXRlLmNzdihmaWxsZWRfaXNvY2hyb25lX2ZyYW1lLCAnSnVuZTJfaXNvY2hyb25lX2ZyYW1lLmNzdicsIHJvdy5uYW1lcyA9IEZBTFNFKQ0KDQpgYGANCg0KDQojIyA0KSBJbnRlcmFjdGl2ZSBWaXN1YWxpemF0aW9uDQpgYGB7cn0NCiMgam9pbiBmYWN0b3IgYW5kIGdlb21ldHJ5IGRhdGEgDQpzY29yZXNfdml6X2ZyYW1lIDwtIGxlZnRfam9pbih2YW5jb3V2ZXJfc2hhcGUsIGZpbGxlZF9zY29yZXNfbG9uZywgYnkgPSBjKCdEQlVJRCcgPSAnZnJvbUlkJykpDQppc29jaHJvbmVfdml6X2ZyYW1lIDwtIGxlZnRfam9pbih2YW5jb3V2ZXJfc2hhcGUsIGZpbGxlZF9pc29jaHJvbmVfZnJhbWUsIGJ5ID0gYygnREJVSUQnID0gJ2Zyb21JZCcpKQ0KDQojIGNvbnZlcnQgYmFjayB0byBzZiBvYmplY3QNCnNjb3Jlc192aXpfZnJhbWVfc2YgPC0gc3RfYXNfc2Yoc2NvcmVzX3Zpel9mcmFtZSkNCnNjb3Jlc192aXpfZnJhbWVfc3QgPC0gc3RfdHJhbnNmb3JtKHNjb3Jlc192aXpfZnJhbWVfc2YsIGNycyA9IDQzMjYpDQoNCmlzb2Nocm9uZV92aXpfZnJhbWVfc2YgPC0gc3RfYXNfc2YoaXNvY2hyb25lX3Zpel9mcmFtZSkNCmlzb2Nocm9uZV92aXpfZnJhbWVfc3QgPC0gc3RfdHJhbnNmb3JtKGlzb2Nocm9uZV92aXpfZnJhbWVfc2YsIGNycyA9IDQzMjYpDQoNCmBgYA0KDQoNCmBgYHtyfQ0KIyBNYXBwaW5nIGZ1bmN0aW9uDQptYXBfbWFrZXJfc2NvcmVzIDwtIGZ1bmN0aW9uKGRhdGEsIGFtZW5pdHksIHdlaWdodCwgbmVhcmVzdF9uKSB7DQogIA0KICBhbW5fbmFtZSA8LSBhbWVuaXR5ICU+JQ0KICAgICAgICAgICAgICAgIHN0cl90b190aXRsZSgpICU+JQ0KICAgICAgICAgICAgICAgIHN0cl9yZXBsYWNlX2FsbCgnT3InLCAnb3InKSAlPiUNCiAgICAgICAgICAgICAgICBzdHJfcmVwbGFjZSgnQW5kJywgJ2FuZCcpICU+JQ0KICAgICAgICAgICAgICAgIHN0cl9yZXBsYWNlKCcvUGVyZm9ybWFuY2UnLCAnJykNCiAgDQogIHByaW50KCdDdXJyZW50IE1hcDonKQ0KICBwcmludChnbHVlKCd7YW1uX25hbWV9IFRyYW5zaXQgQWNjZXNzaWJpbGl0eSAtIFdlaWdodGVkICh7d2VpZ2h0fSkgLSBOZWFyZXN0IEFtZW5pdGllcyAoe3N0cl90b191cHBlcihuZWFyZXN0X24pfSknKSkNCg0KICAjIHN1YnNldCBpbmZvDQogIHBvbHlnX3N1YnNldCA8LSBkYXRhW2RhdGEkdHlwZSA9PSBhbWVuaXR5ICYgZGF0YSR3ZWlnaHQgPT0gd2VpZ2h0ICYgZGF0YSRuZWFyZXN0X24gPT0gbmVhcmVzdF9uLCBdDQogIA0KICAjIHNjb3JlIHZlY3Rvcg0KICBzY29yZV92ZWMgPC0gcG9seWdfc3Vic2V0JHNjb3JlDQogIA0KICAjIGNvbG91ciBwYWxldHRlIA0KICBSZDJHbiA8LSBjKCIjZTMwNjA2IiwgIiNmZDhkM2MiLCAiI2ZmZTY2OSIsICIjY2RmZjVlIiwgIiM2NGVkNTYiKQ0KICBwYWxfZnVuIDwtIGNvbG9yUXVhbnRpbGUocGFsZXR0ZSA9IFJkMkduLCBOVUxMLCBuID0gNSkNCiAgDQogICMgcG9wdXAgIyBwZXJjZW50aWxlKHNjb3JlX3ZlYyksDQogIHBlcmNlbnRpbGUgPC0gZWNkZihzY29yZV92ZWMpDQogIHBfcG9wdXAgPC0gcGFzdGUwKCI8aDI+QWNjZXNzaWJpbGl0eSBQZXJjZW50aWxlOiAiLCByb3VuZChwZXJjZW50aWxlKHNjb3JlX3ZlYyksIDIpKjEwMCwgJyUnLCI8L2gyPiIsIA0KICAgICAgICAgICAgICAgICAgICAgICI8YnI+PHN0cm9uZz5CbG9jayBJRDogIiwgcG9seWdfc3Vic2V0JERCVUlELCI8L3N0cm9uZz4iLA0KICAgICAgICAgICAgICAgICAgICAgICI8YnI+PHN0cm9uZz5CbG9jayBQb3B1bGF0aW9uOiAiLCBwb2x5Z19zdWJzZXQkcG9wLCI8L3N0cm9uZz4iLA0KICAgICAgICAgICAgICAgICAgICAgICI8YnI+UmF3IFNjb3JlOiAiLCByb3VuZChzY29yZV92ZWMsIDIpKQ0KICAgICAgICANCiAgbWFwIDwtIGxlYWZsZXQoZGF0YSA9IHBvbHlnX3N1YnNldCkgJT4lDQogICAgICBhZGRQb2x5Z29ucygNCiAgICAgICAgc3Ryb2tlID0gRkFMU0UsICAjIHJlbW92ZSBwb2x5Z29uIGJvcmRlcnMNCiAgICAgICAgZmlsbENvbG9yID0gfnBhbF9mdW4oc2NvcmVfdmVjKSwgIyBzZXQgZmlsbCBjb2xvdXIgd2l0aCBwYWxsZXR0ZSBmeG4gZnJvbSBhYm9jDQogICAgICAgIGZpbGxPcGFjaXR5ID0gMC42LCBzbW9vdGhGYWN0b3IgPSAwLjUsICMgYWVzdGhldGljcw0KICAgICAgICBwb3B1cCA9IHBfcG9wdXApICU+JSAjIGFkZCBtZXNzYWdlIHBvcHVwIHRvIGVhY2ggYmxvY2sNCiAgICAgIGFkZFRpbGVzKCkgJT4lDQogICAgICBzZXRWaWV3KGxuZyA9IC0xMjIuOCwgbGF0ID0gNDkuMiwgem9vbSA9IDExKSAlPiUNCiAgICAgIGFkZExlZ2VuZCgiYm90dG9tbGVmdCIsICAjIGxvY2F0aW9uDQogICAgICAgICAgICAgICAgcGFsPXBhbF9mdW4sICAgICMgcGFsZXR0ZSBmdW5jdGlvbg0KICAgICAgICAgICAgICAgIHZhbHVlcz1+c2NvcmVfdmVjLCAgIyB2YWx1ZSB0byBiZSBwYXNzZWQgdG8gcGFsZXR0ZSBmdW5jdGlvbg0KICAgICAgICAgICAgICAgIHRpdGxlID0gZ2x1ZSgne2Ftbl9uYW1lfSBUcmFuc2l0IEFjY2VzcycpKQ0KICANCiAgZmlsZV9uYW1lIDwtIGdsdWUoJ3thbW5fbmFtZX0gVHJhbnNpdCBBY2Nlc3NpYmlsaXR5IC0gV2VpZ2h0ZWQgKHt3ZWlnaHR9KSAtIE5lYXJlc3QgQW1lbml0aWVzICh7c3RyX3RvX3VwcGVyKG5lYXJlc3Rfbil9KScpDQogIA0KICBtYXANCiAgI21hcHNob3QobWFwLCB1cmwgPSBwYXN0ZTAoZ2V0d2QoKSwgZ2x1ZSgiL05ldyBIVE1MIE1hcHMve2ZpbGVfbmFtZX0uaHRtbCIpKSkNCg0KfQ0KDQoNCg0KbWFwX21ha2VyX2lzb2Nocm9uZSA8LSBmdW5jdGlvbihkYXRhLCBhbWVuaXR5KSB7DQogIA0KICBhbW5fbmFtZSA8LSBhbWVuaXR5ICU+JQ0KICAgICAgICAgICAgICAgIHN0cl90b190aXRsZSgpICU+JQ0KICAgICAgICAgICAgICAgIHN0cl9yZXBsYWNlX2FsbCgnT3InLCAnb3InKSAlPiUNCiAgICAgICAgICAgICAgICBzdHJfcmVwbGFjZSgnQW5kJywgJ2FuZCcpICU+JQ0KICAgICAgICAgICAgICAgIHN0cl9yZXBsYWNlKCcvUGVyZm9ybWFuY2UnLCAnJykNCiAgDQogIHByaW50KCdDdXJyZW50IE1hcDonKQ0KICBwcmludChnbHVlKCd7YW1uX25hbWV9IFRyYW5zaXQgSXNvY2hyb25lJykpDQoNCiAgIyBzdWJzZXQgaW5mbw0KICBwb2x5Z19zdWJzZXQgPC0gZGF0YVtkYXRhJHR5cGUgPT0gYW1lbml0eSwgXQ0KICANCiAgIyBzY29yZSB2ZWN0b3INCiAgdGltZV9ncm91cHMgPC0gcG9seWdfc3Vic2V0JHRpbWVfZ3JvdXBzDQogIA0KICAjIGNvbG91ciBwYWxldHRlIA0KICBwYWxfZnVuIDwtIGNvbG9yRmFjdG9yKA0KICAgIHBhbGV0dGUgPSBjKCIjM2VmMDAwIiwgIiNjNWViMDAiLCAiI2ZiZmYwMCIsICIjZTljYjAwIiwgIiNlNzg2MDAiLCAiI2U0NDIwMCIsICIjZTIwMDAwIiksDQogICAgbGV2ZWxzID0gc29ydCh1bmlxdWUocG9seWdfc3Vic2V0JHRpbWVfZ3JvdXBzKSkNCiAgICApDQoNCiAgcF9wb3B1cCA8LSBwYXN0ZTAoIjxoMj5UaW1lIHRvIHRoZSBuZWFyZXN0OjwvaDI+IiwgDQogICAgICAgICAgICAgICAgICAgICAgIjxicj48c3Ryb25nPiIsIGFtbl9uYW1lLCI8L3N0cm9uZz4iLA0KICAgICAgICAgICAgICAgICAgICAgICI8YnI+IiwNCiAgICAgICAgICAgICAgICAgICAgICAiPGJyPiA9ICIsIHRpbWVfZ3JvdXBzKQ0KICAgICAgICANCiAgbWFwIDwtIGxlYWZsZXQoZGF0YSA9IHBvbHlnX3N1YnNldCkgJT4lDQogICAgICBhZGRQb2x5Z29ucygNCiAgICAgICAgc3Ryb2tlID0gRkFMU0UsICAjIHJlbW92ZSBwb2x5Z29uIGJvcmRlcnMNCiAgICAgICAgZmlsbENvbG9yID0gfnBhbF9mdW4odGltZV9ncm91cHMpLCAjIHNldCBmaWxsIGNvbG91ciB3aXRoIHBhbGxldHRlIGZ4biBmcm9tIGFib2MNCiAgICAgICAgZmlsbE9wYWNpdHkgPSAwLjcsIHNtb290aEZhY3RvciA9IDAuNSwgIyBhZXN0aGV0aWNzDQogICAgICAgIHBvcHVwID0gcF9wb3B1cCkgJT4lICMgYWRkIG1lc3NhZ2UgcG9wdXAgdG8gZWFjaCBibG9jaw0KICAgICAgYWRkVGlsZXMoKSAlPiUNCiAgICAgIHNldFZpZXcobG5nID0gLTEyMi44LCBsYXQgPSA0OS4yLCB6b29tID0gMTEpICU+JQ0KICAgICAgYWRkTGVnZW5kKCJib3R0b21sZWZ0IiwgICMgbG9jYXRpb24NCiAgICAgICAgICAgICAgICBwYWw9cGFsX2Z1biwgICAgIyBwYWxldHRlIGZ1bmN0aW9uDQogICAgICAgICAgICAgICAgdmFsdWVzPX50aW1lX2dyb3VwcywgICMgdmFsdWUgdG8gYmUgcGFzc2VkIHRvIHBhbGV0dGUgZnVuY3Rpb24NCiAgICAgICAgICAgICAgICB0aXRsZSA9IGdsdWUoJ3thbW5fbmFtZX0gVHJhbnNpdCBBY2Nlc3MnKSkNCiAgDQogIGZpbGVfbmFtZSA8LSBnbHVlKCd7YW1uX25hbWV9IFRyYW5zaXQgSXNvY2hyb25lJykNCiAgDQogIG1hcA0KICAjbWFwc2hvdChtYXAsIHVybCA9IHBhc3RlMChnZXR3ZCgpLCBnbHVlKCIvTmV3IEhUTUwgTWFwcy97ZmlsZV9uYW1lfS5odG1sIikpKQ0KDQp9DQoNCg0KYGBgDQoNCmBgYHtyfQ0KbWFwX21ha2VyX2lzb2Nocm9uZShpc29jaHJvbmVfdml6X2ZyYW1lX3N0LCAnZ2FsbGVyeScpDQoNCmBgYA0KDQoNCg0KIyMgNSkgTWFwIEhUTUwgRXhwb3J0cw0KDQpgYGB7cn0NCg0KZm9yIChhbWVuaXR5IGluIHVuaXF1ZShzY29yZXNfdml6X2ZyYW1lX3N0JHR5cGUpKSB7IA0KICANCiAgIyA0IGlzb2Nocm9uZSBtYXBzDQogIG1hcF9tYWtlcl9pc29jaHJvbmUoZGF0YSA9IGlzb2Nocm9uZV92aXpfZnJhbWVfc3QsIGFtZW5pdHkpDQoNCiAgZm9yICh3ZWlnaHQgaW4gdW5pcXVlKHNjb3Jlc192aXpfZnJhbWVfc3Qkd2VpZ2h0KSkgew0KICAgIGZvciAobmVhcmVzdF9uIGluIHVuaXF1ZShzY29yZXNfdml6X2ZyYW1lX3N0JG5lYXJlc3RfbikpIHsNCiAgICAgIA0KICAgICAgIyAxNiBvdGhlcnMNCiAgICAgIG1hcF9tYWtlcl9zY29yZXMoZGF0YSA9IHNjb3Jlc192aXpfZnJhbWVfc3QsIGFtZW5pdHksIHdlaWdodCwgbmVhcmVzdF9uKQ0KICAgICAgDQogICAgfQ0KICB9DQp9DQpgYGANCg0KDQoNCg==